package vfs.service.impl;

import vfs.obj.association.VFSUserToHomeDirectory;
import java.sql.Connection;
import vfs.obj.entity.VFSUser;
import vfs.filter.VFSFilter.VFSFilterOperator;
import vfs.filter.impl.VFSFilterImpl;
import vfs.filter.VFSFilter;
import vfs.service.VFSNodeService;
import vfs.obj.association.VFSNodeToParent;
import vfs.obj.entity.VFSNodeType;
import vfs.obj.association.VFSNodeToNodeType;
import java.util.List;
import java.util.ArrayList;
import vfs.obj.entity.VFSPathDescendant;
import vfs.obj.entity.VFSPathAncestor;
import java.util.Iterator;
import vfs.except.VFSCreationException;
import vfs.obj.association.VFSPath;
import vfs.obj.association.impl.VFSAssociationFactoryImpl;
import vfs.obj.association.VFSAssociationFactory;
import vfs.obj.entity.VFSNode;
import vfs.except.VFSEntityException;
import vfs.except.VFSModificationException;
import vfs.except.VFSUncheckedException;
import vfs.obj.entity.VFSEntityFactory;
import vfs.obj.entity.impl.VFSEntityFactoryImpl;
import vfs.persist.util.VFSPersistUtil;
import vfs.util.VFSUtil;

/**
 *
 * @author Matthew Eavenson <durandal@uga.edu>
 * 
 */
public class VFSNodeServiceImpl implements VFSNodeService{
    
    private Connection conn;
    private VFSNode root;
    private VFSUser user;
    private VFSEntityFactory eFactory;
    private VFSAssociationFactory aFactory;
    
    public VFSNodeServiceImpl(Connection conn, VFSUser user){
        this.conn = conn;
        if(user == null) throw new VFSEntityException("Invalid user");
        this.user = user;
        eFactory = new VFSEntityFactoryImpl(conn);
        aFactory = new VFSAssociationFactoryImpl(conn);
        VFSFilter filter = new VFSFilterImpl();
        filter.addConstraint("id", "1", VFSFilterOperator.EQ);
        Iterator<VFSNode> nodes = eFactory.findNodes(filter);
        if(nodes.hasNext()) root = nodes.next();        
    }
    
    @Override
    public VFSNodeType getType(VFSNode node){
        VFSNodeType nodeType = null;
        VFSNodeToNodeType nToType = aFactory.getNodeToNodeType();
        nodeType = nToType.getNodeType(node);
        return nodeType;
    }
    
    @Override
    public VFSNode getNode(VFSNode workingDir, String directory){
        VFSNode node = null;
        VFSPath path = aFactory.getPath();
        node = resolveDir(path, workingDir, directory);
        return node;
    }
    
    @Override
    public VFSNode getNode(String directory){
        VFSPath path = aFactory.getPath();
        VFSUserToHomeDirectory uToHome = aFactory.getUserToHomeDirectory();
        VFSNode node = resolveDir(path, uToHome.getHomeDirectory(user), directory);
        return node;
    }
    
    @Override
    public Iterator<VFSNode> getNodes(VFSFilter filter){
        Iterator<VFSNode> nodes = eFactory.findNodes(filter);
        return nodes;
    }

    @Override
    public Iterator<VFSNode> getNodes(VFSNodeType nodeType, VFSFilter filter){
        VFSNodeToNodeType nToType = aFactory.getNodeToNodeType();
        Iterator<VFSNode> nodes = nToType.getNodes(nodeType, filter);
        return nodes;
    }
    
    @Override
    public boolean hasChildren(VFSNode node){
        VFSPath path = aFactory.getPath();
        Iterator<VFSNode> children = path.getDescendants(node, 1);
        return children.hasNext();
    }
    
    @Override
    public boolean exists(String pathStr){
        if(pathStr.startsWith("/")){
            return exists(root, pathStr);
        }else{
            VFSUserToHomeDirectory uToHome = aFactory.getUserToHomeDirectory();
            VFSNode home = uToHome.getHomeDirectory(user);
            return exists(home, pathStr);
        }   
    }

    @Override
    public boolean exists(VFSNode workingDir, String pathStr){
        VFSPath path = aFactory.getPath();
        return resolveDir(path, workingDir, pathStr) != null;
    }
    
    @Override
    public VFSNode create(String pathStr, String access, String location, VFSNodeType nodeType){
        
        if(pathStr.startsWith("/")){
            return create(root, pathStr, access, location, nodeType);
        }else{
            VFSUserToHomeDirectory uToHome = aFactory.getUserToHomeDirectory();
            VFSNode home = uToHome.getHomeDirectory(user);
            return create(home, pathStr, access, location, nodeType);
        }   
    }
    
    @Override
    public VFSNode create( VFSNode workingDir, String name, String access, String location, VFSNodeType nodeType ){
        VFSNode node = null;
        if(workingDir != null){
            VFSPersistUtil.disableAutoCommit(conn);
            try{
                if(name.contains("/")){
                    VFSPath path = aFactory.getPath();
                    node = createRecursive(eFactory, path, workingDir, name);
                }else{
                    node = eFactory.createNode(name, location, VFSUtil.getPublicAccess(access), user, workingDir, false);
                }
                // if node type is given, node is not supposed to be a folder, so set its type
                if(nodeType != null){
                    VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
                    nft.setNodeType(node, nodeType);
                }
            }catch(VFSUncheckedException ex){
                VFSPersistUtil.rollback(conn);
                VFSPersistUtil.enableAutoCommit(conn);
                throw ex;
            }
            VFSPersistUtil.commit(conn);
            VFSPersistUtil.enableAutoCommit(conn);
        }else throw new VFSEntityException("No such file or directory");
        return node;
    }
    
    @Override
    public boolean remove( String path){
        return remove(root, path, false);
    }
    
    @Override
    public boolean remove( String path, boolean recursive ){
        return remove(root, path, recursive);
    }
    
    @Override
    public boolean remove( VFSNode node ){
        return remove(node, null, false);
    }
    
    @Override
    public boolean remove( VFSNode node, boolean recursive ){
        return remove(node, null, recursive);
    }
    
    @Override
    public boolean remove( VFSNode workingDir, String pathStr ){
        return remove(workingDir, pathStr, false);
    }
    
    @Override
    public boolean remove( VFSNode workingDir, String pathStr, boolean recursive ){
        VFSPath path = aFactory.getPath();
        if(pathStr != null) workingDir = resolveDir(path, workingDir, pathStr);

        if(workingDir != null){
            VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
            if(nft.getNodeType(workingDir) == null && !recursive){
                throw new VFSEntityException(workingDir.getName()+": is a directory");
            }else if(workingDir.equals(root)){
                throw new VFSModificationException("Attempted removal of the root node");
            }else{
                eFactory.removeNode(workingDir);
                return true;
            }
        }
        return false;
    }
    
    @Override
    public Iterator<VFSNode> list( VFSNode workingDir, String pathStr){
        VFSPath path = aFactory.getPath();

        if(pathStr != null) workingDir = resolveDir(path, workingDir, pathStr);

        if(workingDir != null){
            return path.getDescendants(workingDir, 1);
        }else throw new VFSEntityException("No such file or directory");
    }
    
    @Override
    public boolean rename( VFSNode node, String name ){
        node.setName(name);
        eFactory.storeNode(node);
        return true;
    }
    
    @Override
    public boolean move( VFSNode workingDir, String srcPathStr, String destPathStr ){
        VFSPath path = aFactory.getPath();
        VFSNode node = resolveDir(path, workingDir, srcPathStr);
        VFSNode destination = resolveDir(path, workingDir, destPathStr);

        if(node == null){
            throw new VFSEntityException( srcPathStr+": No such file or directory" );
        }else if(destination == null){
            throw new VFSEntityException( destPathStr+": No such file or directory" );
        }else return move(node, destination);
    }
    
    @Override
    public boolean move( VFSNode src, VFSNode dest){        
        VFSNodeToParent ntp = aFactory.getNodeToParent();
        // if both nodes exist
        if(src != null && dest != null){

            // check to make sure the node we're moving to is actually a folder
            VFSNodeToNodeType nft = aFactory.getNodeToNodeType();
            if(nft.getNodeType(dest) != null){
                throw new VFSEntityException("Destination is not a folder");
            }else{
                VFSPath path = aFactory.getPath();

                // cache the list of ancestors, since we're potentially going to be using it a lot
                List<VFSPathAncestor> ancestorList = new ArrayList<VFSPathAncestor>();
                for(Iterator<VFSPathAncestor> ancestors = path.getAncestors(dest, null); ancestors.hasNext();){
                    ancestorList.add(ancestors.next());
                }

                // iterate through the node's descendants, removing all ancestors above the
                // node's level, and adding the list of new ancestors
                for(Iterator<VFSPathDescendant> descendants = path.getDescendants(src, null); descendants.hasNext();){
                    VFSPathDescendant descendant = descendants.next();
                    VFSNode descNode = descendant.getDescendant();
                    int length = descendant.length();
                    path.removeAncestors(descNode, length+1);

                    try{
                        // add new ancestors above the new parent
                        for(VFSPathAncestor ancestor: ancestorList){
                            path.addPath(ancestor.getAncestor(), descNode, ancestor.length()+length+1);
                        }
                        // add the new parent as an ancestor
                        path.addPath(dest, descNode, length+1);
                    }catch(VFSCreationException ex){
                        throw new VFSModificationException(ex);
                    }
                }

                // remove all the old ancestors from this node and add the new ancestors
                path.removeAncestors(src, 1);
                try{
                    for(VFSPathAncestor ancestor: ancestorList){
                        path.addPath(ancestor.getAncestor(), src, ancestor.length()+1);
                    }
                    path.addPath(dest, src, 1);
                }catch(VFSCreationException ex){
                    throw new VFSModificationException(ex);
                }

                // update the parent foreign key
                ntp.setParent(src, dest);
                return true;
            }
        }else if(src == null) throw new VFSEntityException("Source does not exist");
        else if(dest == null) throw new VFSEntityException("Destination does not exist");
        return false;
    }
    
    private VFSNode resolveDir(VFSPath path, VFSNode workingDir, String directory){
        VFSNode[] nodeArr = resolvePath(path, workingDir, directory);
        return nodeArr[nodeArr.length-1];
    }
    
    private String[] normalizePath(String path, VFSNode workingDir){
        // if the path ends with a slash, chop it off
        path = path.endsWith("/")? path.substring(0, path.length()-1): path;
        // determine whether this path should be relative to the root
        boolean absolute = path.startsWith("/");
        // split the path on any slashes
        String[] parsedArr = path.split("/");
        // if the array has no indices, it was only a slash, so return it
        if(parsedArr.length == 0) return parsedArr;
        
        // set the working directory based on whether the path was absolute
        workingDir = absolute? root: workingDir;
        
        if(!parsedArr[0].isEmpty()){ // if the first index wasn't empty (i.e. not the root)
            // make a new starting index and add the working directory to it
            String[] pathArr = new String[parsedArr.length+1];
            pathArr[0] = workingDir.getName();
            for(int i=0; i<parsedArr.length; i++) pathArr[i+1] = parsedArr[i];
            return pathArr;
        }
        return parsedArr;
    }
    
    private VFSNode[] resolvePath(VFSPath path, VFSNode workingDir, String directory){
        // if there's no path, return the working directory
        if(directory == null) return new VFSNode[]{ workingDir };
        // normalize the path to include the working directory, if this path isn't absolute
        String[] pathArr = normalizePath(directory, workingDir);
        // if we get back a 0 length array, the path was a slash only, so return the root
        if(pathArr.length == 0) return new VFSNode[]{ root };
        
        boolean absolute = directory.startsWith("/");
        workingDir = absolute? root: workingDir;
        VFSNode[] nodeArr = new VFSNode[pathArr.length];
        nodeArr[0] = workingDir;
                
        for(int i=1; i<pathArr.length; i++){
            String folder = pathArr[i];
            if(workingDir == null) break;
            if(folder.equals("..")){
                workingDir = path.getAncestor(workingDir, 1);
            }else if(folder.equals(".")) {
            }else{
                VFSFilter filter = new VFSFilterImpl();
                filter.addConstraint("nodename", folder, VFSFilterOperator.EQ);
                filter.addConstraint("length", "1", VFSFilterOperator.EQ);
                Iterator<VFSPathDescendant> children = path.getDescendants(workingDir, filter);
                if(children.hasNext())
                    workingDir = children.next().getDescendant();
                else workingDir = null;
            }
            nodeArr[i] = workingDir;
        }
        
        return nodeArr;
    }
    
    private VFSNode createRecursive( VFSEntityFactory eFactory, VFSPath path, VFSNode workingDir, String directory){
        String[] pathArr = normalizePath(directory, workingDir);
        boolean absolute = directory.startsWith("/");
        workingDir = absolute? root: workingDir;
        
        VFSNode[] nodeArr = resolvePath(path, workingDir, directory);
        VFSNode parent = workingDir;
        for(int i=1; i<nodeArr.length; i++){
            VFSNode node = nodeArr[i];
            if(node != null) parent = node;
            else parent = eFactory.createNode(pathArr[i], null, VFSNode.PublicAccess.READ, user, parent, false);
        }
        return parent;
    }
    
    @Override
    public String getPath(VFSNode node){
        StringBuilder sb = new StringBuilder();
        VFSPath path = aFactory.getPath();
        VFSFilter filter = new VFSFilterImpl();
        filter.addConstraint("length", "DESC", VFSFilterOperator.SORTBY);
        for(Iterator<VFSPathAncestor> ancestors = path.getAncestors(node, filter); ancestors.hasNext();){
            VFSNode ancestor = ancestors.next().getAncestor();
            sb.append(ancestor.getName()+"/");
        }
        sb.append(node.getName());
        return sb.toString();
    }
    
}
